package com.northpool.operator.common.algorithm;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.northpool.operator.annotation.InputParam;
import com.northpool.operator.annotation.InputParams;
import com.northpool.operator.annotation.OperatorInfo;
import com.northpool.operator.annotation.OutputParam;
import com.northpool.operator.annotation.OutputParams;
import com.northpool.operator.annotation.ParamEnum;
import com.northpool.operator.annotation.ParamEnums;
import com.northpool.operator.common.algorithm.annotation.PluginInfo;
import com.northpool.operator.common.minio.MinIOUtil;
import com.northpool.operator.type.TypeEnum;
import com.northpool.operator.yaml.InputParameter;
import com.northpool.operator.yaml.NodeDef;
import com.northpool.operator.yaml.NodeDsl;
import com.northpool.operator.yaml.OutputParameter;
import com.northpool.operator.yaml.Spec;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

public class PluginXmlGenerator {
    protected static String outputFilePath = "d:\\temp\\进程级算子";
    protected static String outputDslFilePath = "d:\\temp\\dsl";
    protected static String modulePath = "";
    public static boolean isDsl = true;
    public static String modelName;
    public static String modelVersion;

    public static String s3Endpoint = "http://northpool.work:9000";
    public static String bucket = "stp-minio";
    public static String s3AccessKey = "northpool.stp";
    public static String s3SecretKey = "z6FFgHDXmzRq8sCAcqIiYgcOWfgDHRFCUc7fR1zK";

    public static MinIOUtil minIOUtil = new MinIOUtil(s3Endpoint, s3AccessKey, s3SecretKey);

    public PluginXmlGenerator() {
    }

    public static void main(String[] args) {
        String modelName = args[0];
        String version = args[1];

        s3Endpoint = args[2];
        bucket = args[3];
        s3AccessKey = args[4];
        s3SecretKey = args[5];

        Console.log("对象存储地址： {}", s3Endpoint);
        minIOUtil = new MinIOUtil(s3Endpoint, s3AccessKey, s3SecretKey);
        exportPlugin(modelName, version);
    }

    public static void exportPlugin(String moduleName) {
        exportPlugin(moduleName, "0.0.1");
    }

    public static void exportPlugin(String moduleName, String version) {
        modelName = moduleName;
        modelVersion = version;
        PluginXmlGenerator generator = new PluginXmlGenerator();
        String userDir = System.getProperty("user.dir");

        modulePath = userDir + "\\" + moduleName;

        outputFilePath = outputFilePath + "\\" + moduleName + "\\";
        File dir = new File(outputFilePath);
        if (!dir.exists()) {
            dir.mkdir();
        }

        File classesDir = new File(modulePath + "\\build\\classes\\java\\main");
        generator.scanClassesInDir(classesDir);
        try {
            Thread.sleep(1000);
            System.exit(0);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }

    public void scanClassesInDir(File dir) {
        if (!dir.exists() || !dir.isDirectory()) {
            System.out.println("Directory does not exist or is not a directory: " + dir);
            return;
        }

        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    scanClassesInDir(file); // 递归扫描子目录
                } else if (file.getName().endsWith(".class")) {
                    processClassFile(file);
                }
            }
        } else {
            System.out.println("No files found in directory: " + dir);
        }
    }


    private void processClassFile(File classFile) {
        try {
            String className = getClassName(classFile);
            Class<?> clazz = loadClass(className, classFile);

            if (clazz.isAnnotationPresent(PluginInfo.class) || clazz.isAnnotationPresent(OperatorInfo.class)) {
                if (isDsl) {
                    generateDslForAnnotatedClasses(clazz);
                } else {
                    PluginInfo pluginInfo = AnnotationUtil.getAnnotation(clazz, PluginInfo.class);
                    // 生成xml
                    File file = generateXmlForAnnotatedClasses(clazz);
                    // copy jar
                    List<String> collect = Arrays.stream(pluginInfo.run().split(" ")).filter(p -> p.endsWith(".jar")).collect(Collectors.toList());
                    String jarName = collect.get(0);
                    String jarPath = modulePath + File.separator + "build\\libs\\" + jarName;
                    // zip压缩
                    ZipUtil.zip(FileUtil.file(outputFilePath + pluginInfo.name() + ".zip"), StandardCharsets.UTF_8, false,
                            new File(jarPath),
                            file
                    );
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Error processing class file: " + classFile, e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private String getClassName(File classFile) {
        String path = classFile.getAbsolutePath();
        int beginIndex = path.indexOf("com" + File.separator + "northpool");
        if (beginIndex < 0) {
            throw new RuntimeException("类路径不正确，必需使用 com/northpool");
        }
        String relativePath = path.substring(beginIndex, path.lastIndexOf(".class"));
        return relativePath.replace(File.separatorChar, '.');
    }


    private Class<?> loadClass(String className, File classFile) throws Exception {
        URL[] urls = new URL[]{classFile.getParentFile().toURI().toURL()};
        CustomClassLoader customClassLoader = new CustomClassLoader(urls);
        return customClassLoader.findClass(classFile, className);
    }

    public static NodeDef buildNodeDef(PluginInfo pluginInfo, OperatorInfo operatorInfo) {
        NodeDef nodeDef = new NodeDef();
        if (pluginInfo != null) {
            nodeDef.setName(pluginInfo.name());
            nodeDef.setDescription(pluginInfo.description());
            nodeDef.setDeprecated(false);
            nodeDef.setRef(modelName + ":" + pluginInfo.ref());
            nodeDef.setCreatorRef(pluginInfo.writer()); // 后续挂标识
            nodeDef.setCreatorName(pluginInfo.writer());
            nodeDef.setLatestVersion(pluginInfo.version());
            nodeDef.setSourceLink("");
            nodeDef.setDocumentLink("");
        } else if (operatorInfo != null) {
            nodeDef.setName(operatorInfo.name());
            nodeDef.setDescription(operatorInfo.description());
            nodeDef.setDeprecated(operatorInfo.deprecated());
            nodeDef.setRef(modelName + ":" + operatorInfo.ref());
            nodeDef.setCreatorRef(operatorInfo.writer()); // 后续挂标识
            nodeDef.setCreatorName(operatorInfo.writer());
            nodeDef.setLatestVersion(operatorInfo.version());
            nodeDef.setSourceLink(operatorInfo.sourceLink());
            nodeDef.setDocumentLink(operatorInfo.documentLink());
        } else {
            throw new RuntimeException("算子类描述异常，没有描述注解");
        }
        nodeDef.setType("PROCESS");
        nodeDef.setOwnerType("ORGANIZATION");
        nodeDef.setOwnerRef("piesat");
        nodeDef.setOwnerName("航天宏图");
        nodeDef.setVisibleType("PUBLIC");
        String defaultIcon = "/" + bucket + "/images/" + "node_default.png";
        nodeDef.setIcon(defaultIcon);
        return nodeDef;
    }

    public static NodeDsl buildNodeDsl(PluginInfo pluginInfo, OperatorInfo operatorInfo) {
        NodeDsl nodeDsl = new NodeDsl();
        if (pluginInfo != null) {
            nodeDsl.setRef("piesat/" + modelName + ":" + pluginInfo.ref());
            nodeDsl.setName(pluginInfo.name());
            nodeDsl.setDescription(pluginInfo.description());
            nodeDsl.setVersion(pluginInfo.version());
        } else if (operatorInfo != null) {
            nodeDsl.setRef("piesat/" + modelName + ":" + operatorInfo.ref());
            nodeDsl.setName(operatorInfo.name());
            nodeDsl.setDescription(operatorInfo.description());
            nodeDsl.setVersion(operatorInfo.version());
        } else {
            throw new RuntimeException("算子类描述异常，没有描述注解");
        }
        return nodeDsl;
    }

    public static void generateDslForAnnotatedClasses(Class<?> clazz) {
        PluginInfo pluginInfo = AnnotationUtil.getAnnotation(clazz, PluginInfo.class);
        OperatorInfo operatorInfo = AnnotationUtil.getAnnotation(clazz, OperatorInfo.class);
        NodeDef nodeDef = buildNodeDef(pluginInfo, operatorInfo);
        NodeDsl nodeDsl = buildNodeDsl(pluginInfo, operatorInfo);

        Spec spec = new Spec();
        // cmd
//        String run = pluginInfo.run();
        HashMap<String, String> apps = new LinkedHashMap<>();
        apps.put(modelName, "piesat/" + modelName + "/" + modelVersion);

        LinkedList<String> cmd = new LinkedList<>();
        cmd.add("${JAVA_EXE}");
        cmd.add("-jar");
        cmd.add(String.format("${%s}/%s-%s.jar", modelName, modelName, modelVersion));
        cmd.add(clazz.getName());
        spec.setCmd(cmd);
        LinkedHashMap<String, String> runtimes = new LinkedHashMap<>();
        runtimes.put("java", "java/8");
        spec.setRuntimes(runtimes);
        spec.setApps(apps);
        nodeDsl.setSpec(spec);
        nodeDsl.setResultFile("result_file");

        List<InputParameter> inputParameters = new LinkedList<>();
        InputParams inputParams = AnnotationUtil.getAnnotation(clazz.getConstructors()[0], InputParams.class);
        InputParam[] inputParamAnnotations = inputParams != null ?  inputParams.value() : new InputParam[0];
        for (InputParam item : inputParamAnnotations) {
            InputParameter inputParameter = new InputParameter();
            inputParameter.setRef(item.ref());
            inputParameter.setName(item.name());
            inputParameter.setDescription(item.desc());
            inputParameter.setRequired(item.required());
            // 非必需时才允许默认值
            if (!item.required()) {
                inputParameter.setValue(item.value());
            }
            if (TypeEnum.ENUM.equals(item.type()) && StrUtil.isNotBlank(item.enumRef())) {
                for (ParamEnums anEnum : operatorInfo.enums()) {
                    if (anEnum.ref().equals(item.enumRef())) {
                        ParamEnum[] value = anEnum.value();
                        List<com.northpool.operator.yaml.ParamEnum> collect = Arrays.stream(value).map(
                                p -> new com.northpool.operator.yaml.ParamEnum(p.ref(), p.name(), p.description())).collect(Collectors.toList());
                        inputParameter.setEnums(collect);
                    }
                }
            }
            inputParameter.setType(item.type().name());
            inputParameters.add(inputParameter);
        }
        nodeDsl.setInputParameters(inputParameters);

        List<OutputParameter> outputParameters = new LinkedList<>();
        OutputParams outParams = AnnotationUtil.getAnnotation(clazz, OutputParams.class);
        if (outParams != null) {
            for (OutputParam item : outParams.value()) {
                OutputParameter outputParameter = new OutputParameter();
                outputParameter.setRef(item.ref());
                outputParameter.setName(item.name());
                outputParameter.setDescription(item.desc());
                outputParameter.setType(item.type().name());
                outputParameter.setRequired(item.required());
                outputParameters.add(outputParameter);
            }
            nodeDsl.setOutputParameters(outputParameters);
        }

        buildYaml(nodeDef, nodeDsl);
    }

    public static void buildYaml(NodeDef nodeDef, NodeDsl nodeDsl) {
        DumperOptions options = new DumperOptions();
        options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); // 使用块风格
        options.setPrettyFlow(true); // 使输出更易读
        options.setIndent(2); // 设置缩进为2个空格
        options.setIndentWithIndicator(true);// -后的缩进
        options.setIndicatorIndent(2);
        options.setAllowUnicode(true); // 允许Unicode字符

        Yaml yaml = new Yaml(options);

        // 去除null值并保持字段顺序
        JSON.DEFAULT_GENERATE_FEATURE &= ~SerializerFeature.SortField.getMask();
        SerializeConfig serializeConfig = new SerializeConfig(true);

        // 写入节点描述
        String nodeJsonStr = JSON.toJSONString(nodeDef, serializeConfig);
        JSONObject nodeJsonObj = JSONObject.parseObject(nodeJsonStr, Feature.OrderedField);
        String nodeYaml = yaml.dumpAs(nodeJsonObj, Tag.MAP, DumperOptions.FlowStyle.BLOCK);
        String nodeYamlObjName = "nodes" + "/" + nodeDef.getOwnerRef() + "/" + nodeDef.getRef() + "/node_def.yml";
        minIOUtil.putObject(bucket, nodeYamlObjName, IoUtil.toUtf8Stream(nodeYaml), "text/plain; charset=utf-8");

        // 写入节点版本描述
        String jsonString = JSON.toJSONString(nodeDsl, serializeConfig);
        JSONObject jsonObject = JSONObject.parseObject(jsonString, Feature.OrderedField);

        String yml = yaml.dumpAs(jsonObject, Tag.MAP, DumperOptions.FlowStyle.BLOCK);
        String nodeVersionYamlObjName = "nodes" + "/" + nodeDef.getOwnerRef() + "/" + nodeDef.getRef() + "/" + nodeDsl.getVersion() + "/node_def.yml";
        minIOUtil.putObject(bucket, nodeVersionYamlObjName, IoUtil.toUtf8Stream(yml), "text/plain; charset=utf-8");

        Console.log("上传完成: {}, 对象存储",nodeVersionYamlObjName);
//        String childPath = nodeDsl.getRef() + "/" + nodeDsl.getVersion() + "/node_def.yml";
//        File file = FileUtil.file(outputDslFilePath, childPath);
    }

    public static File generateXmlForAnnotatedClasses(Class<?> clazz) throws NoSuchMethodException {
        Document document = DocumentHelper.createDocument();

        Element root = document.addElement("plugin");
        // 添加其他属性或子元素

        PluginInfo pluginInfo = AnnotationUtil.getAnnotation(clazz, PluginInfo.class);
        root.addElement("name").addText(pluginInfo.ref());
        root.addElement("version").addText(pluginInfo.version());
        root.addElement("description").addText(pluginInfo.name());
        root.addElement("writer").addText("piesat");
        root.addElement("manual").addText("false");
        root.addElement("maxtime").addText(pluginInfo.maxtime());
        Element OSelement = root.addElement("OSes").addElement("OS");
        OSelement.addElement("type").addText("Linux");
        OSelement.addElement("struct").addText("X86|x64");

        root.addElement("CPU").addText(pluginInfo.CPU());
        root.addElement("GPU").addText(pluginInfo.GPU());
        root.addElement("disk").addText(pluginInfo.disk());
        root.addElement("memory").addText(pluginInfo.memory());
        root.addElement("run").addText(pluginInfo.run());

        Element test = root.addElement("test");
        Element params = test.addElement("params");

        // 参数
        Element startParam = root.addElement("startParam");
        InputParams inputParams = AnnotationUtil.getAnnotation(clazz.getConstructors()[0], InputParams.class);
        if (inputParams != null && inputParams.value() != null) {
            for (InputParam item : inputParams.value()) {
                Element param = startParam.addElement("param");
                param.addElement("name").addText(item.ref()); // 唯一标识
                param.addElement("description").addText(item.name()); // 名称
                param.addElement("memo").addText(item.desc()); // 描述
                param.addElement("inout").addText("in");
                param.addElement("type").addText(item.type().toString());
                param.addElement("info").addText(item.desc());
                Element testParam = params.addElement("param");
                testParam.addElement("id").addText(item.ref());
                testParam.addElement("value").addText(item.testValue());
            }
        }

        OutputParams outParams = AnnotationUtil.getAnnotation(clazz, OutputParams.class);
        if (outParams != null && outParams.value() != null) {
            for (OutputParam item : outParams.value()) {
                Element param = startParam.addElement("param");
                param.addElement("description").addText(item.name());
                param.addElement("name").addText(item.ref());
                param.addElement("inout").addText("out");
                param.addElement("type").addText(item.type().toString());
                param.addElement("info").addText(item.desc());
            }
        }
        File file = new File(outputFilePath + pluginInfo.name());
        file.mkdir();
        String tempPath = file.getAbsolutePath() + File.separator + pluginInfo.name() + "_Plugin.xml";
        saveDocumentToFile(document, tempPath);
        return new File(tempPath);
    }


    private static void saveDocumentToFile(Document document, String filePath) {
        try (FileWriter writer = new FileWriter(filePath)) {
            OutputFormat format = OutputFormat.createPrettyPrint();
            XMLWriter xmlWriter = new XMLWriter(writer, format);
            xmlWriter.write(document);
            xmlWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
